ECFieldElementFp.prototype.getByteLength = function() {
  return Math.floor((this.toBigInteger().bitLength() + 7) / 8)
}
ECPointFp.prototype.getEncoded = function(compressed) {
  var integerToBytes = function(i, len) {
    var bytes = i.toByteArray()
    for (var k = 0; k < bytes.length; k++) {
      bytes[k] &= 0xff
    }
    if (len < bytes.length) {
      bytes = bytes.slice(bytes.length - len)
    } else
      while (len > bytes.length) {
        bytes.unshift(0)
      }
    return bytes
  }
  var x = this.getX().toBigInteger()
  var y = this.getY().toBigInteger()
  var enc = integerToBytes(x, 32)
  if (compressed) {
    if (y.isEven()) {
      enc.unshift(0x02)
    } else {
      enc.unshift(0x03)
    }
  } else {
    enc = enc.concat(integerToBytes(y, 32))
  }
  return enc
}
ECPointFp.decodeFrom = function(curve, enc) {
  var type = enc[0]
  var dataLen = enc.length - 1
  var xBa = enc.slice(1, 1 + dataLen / 2)
  var yBa = enc.slice(1 + dataLen / 2, 1 + dataLen)
  xBa.unshift(0)
  yBa.unshift(0)
  var x = new BigInteger(xBa)
  var y = new BigInteger(yBa)
  return new ECPointFp(curve, curve.fromBigInteger(x), curve.fromBigInteger(y))
}
ECPointFp.decodeFromHex = function(curve, encHex) {
  var type = encHex.substr(0, 2)
  var dataLen = encHex.length - 2
  var xHex = encHex.substr(2, dataLen / 2)
  var yHex = encHex.substr(2 + dataLen / 2, dataLen / 2)
  var x = new BigInteger(xHex, 16)
  var y = new BigInteger(yHex, 16)
  return new ECPointFp(curve, curve.fromBigInteger(x), curve.fromBigInteger(y))
}
ECPointFp.prototype.add2D = function(b) {
  if (this.isInfinity()) return b
  if (b.isInfinity()) return this
  if (this.x.equals(b.x)) {
    if (this.y.equals(b.y)) {
      return this.twice()
    }
    return this.curve.getInfinity()
  }
  var x_x = b.x.subtract(this.x)
  var y_y = b.y.subtract(this.y)
  var gamma = y_y.divide(x_x)
  var x3 = gamma
    .square()
    .subtract(this.x)
    .subtract(b.x)
  var y3 = gamma.multiply(this.x.subtract(x3)).subtract(this.y)
  return new ECPointFp(this.curve, x3, y3)
}
ECPointFp.prototype.twice2D = function() {
  if (this.isInfinity()) return this
  if (this.y.toBigInteger().signum() == 0) {
    return this.curve.getInfinity()
  }
  var TWO = this.curve.fromBigInteger(BigInteger.valueOf(2))
  var THREE = this.curve.fromBigInteger(BigInteger.valueOf(3))
  var gamma = this.x
    .square()
    .multiply(THREE)
    .add(this.curve.a)
    .divide(this.y.multiply(TWO))
  var x3 = gamma.square().subtract(this.x.multiply(TWO))
  var y3 = gamma.multiply(this.x.subtract(x3)).subtract(this.y)
  return new ECPointFp(this.curve, x3, y3)
}
ECPointFp.prototype.multiply2D = function(k) {
  if (this.isInfinity()) return this
  if (k.signum() == 0) return this.curve.getInfinity()
  var e = k
  var h = e.multiply(new BigInteger('3'))
  var neg = this.negate()
  var R = this
  var i
  for (i = h.bitLength() - 2; i > 0; --i) {
    R = R.twice()
    var hBit = h.testBit(i)
    var eBit = e.testBit(i)
    if (hBit != eBit) {
      R = R.add2D(hBit ? this : neg)
    }
  }
  return R
}
ECPointFp.prototype.isOnCurve = function() {
  var x = this.getX().toBigInteger()
  var y = this.getY().toBigInteger()
  var a = this.curve.getA().toBigInteger()
  var b = this.curve.getB().toBigInteger()
  var n = this.curve.getQ()
  var lhs = y.multiply(y).mod(n)
  var rhs = x
    .multiply(x)
    .multiply(x)
    .add(a.multiply(x))
    .add(b)
    .mod(n)
  return lhs.equals(rhs)
}
ECPointFp.prototype.toString = function() {
  return (
    '(' +
    this.getX()
      .toBigInteger()
      .toString() +
    ',' +
    this.getY()
      .toBigInteger()
      .toString() +
    ')'
  )
}
ECPointFp.prototype.validate = function() {
  var n = this.curve.getQ()
  if (this.isInfinity()) {
    throw new Error('Point is at infinity.')
  }
  var x = this.getX().toBigInteger()
  var y = this.getY().toBigInteger()
  if (x.compareTo(BigInteger.ONE) < 0 || x.compareTo(n.subtract(BigInteger.ONE)) > 0) {
    throw new Error('x coordinate out of bounds')
  }
  if (y.compareTo(BigInteger.ONE) < 0 || y.compareTo(n.subtract(BigInteger.ONE)) > 0) {
    throw new Error('y coordinate out of bounds')
  }
  if (!this.isOnCurve()) {
    throw new Error('Point is not on the curve.')
  }
  if (this.multiply(n).isInfinity()) {
    throw new Error('Point is not a scalar multiple of G.')
  }
  return true
}
